// ==UserScript== // @name 🔐 密码填充 // @namespace https://ez118.github.io/ // @version 0.2.4 // @description 为Via设计的第三方密码自动保存/填充工具 // @author ZZY_WISU // @match *://*/* // @license GPLv3 // @icon data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTk2MCA5NjAgOTYwIj4KICA8cGF0aCBmaWxsPSIjODg4IiBkPSJNMTYwLTQ0MHEtNTAgMC04NS0zNXQtMzUtODVxMC01MCAzNS04NXQ4NS0zNXE1MCAwIDg1IDM1dDM1IDg1cTAgNTAtMzUgODV0LTg1IDM1Wk04MC0yMDB2LTgwaDgwMHY4MEg4MFptNDAwLTI0MHEtNTAgMC04NS0zNXQtMzUtODVxMC01MCAzNS04NXQ4NS0zNXE1MCAwIDg1IDM1dDM1IDg1cTAgNTAtMzUgODV0LTg1IDM1Wm0zMjAgMHEtNTAgMC04NS0zNXQtMzUtODVxMC01MCAzNS04NXQ4NS0zNXE1MCAwIDg1IDM1dDM1IDg1cTAgNTAtMzUgODV0LTg1IDM1WiI+PC9wYXRoPgo8L3N2Zz4= // @run-at document-end // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // ==/UserScript== /* =====[ 变量存储 ]===== */ const ICONS = { 'del': '' }; var savedAccount = []; /* ====================== */ function Toast(text) { try{ if (typeof(window.via) == "object") window.via.toast(text); else if (typeof(window.mbrowser) == "object") window.mbrowser.showToast(text); else alert(text); }catch{ alert(text); } } function hash(str) { let hash = 5381; for (let i = 0; i < str.length; i++) { hash = (hash * 33) ^ str.charCodeAt(i); } return hash >>> 0; } function getHost() { return window.location.host; } function isLoginPage() { let forms = document.getElementsByTagName("form"); let isLogin = false; let formPosition = {x: 0, y: 0}; let formobj = null; Array.prototype.forEach.call(forms, (form) => { let hasTextInput = false; let hasPasswordInput = false; // 获取所有 input 元素 let inputs = form.getElementsByTagName("input"); // 检查每个 input 的类型 Array.prototype.forEach.call(inputs, (input) => { if (input.type === "text" || input.type === "email") { hasTextInput = true; } else if (input.type === "password") { hasPasswordInput = true; } }); // 如果同时存在 text 和 password 类型的输入框,认为是登录页面 if (hasTextInput && hasPasswordInput) { isLogin = true; let rectData = form.getClientRects()[0]; formPosition.x = rectData.left + rectData.width / 2 - 90; formPosition.y = rectData.top + rectData.height - 15; formobj = form; } }); return { isLogin, x: formPosition.x, y: formPosition.y, obj: formobj }; } function getFormData(ele){ let inputs = ele.getElementsByTagName("input"); let usr = null; let psw = null; // 检查每个 input 的类型 Array.prototype.forEach.call(inputs, (input) => { if ((input.type === "text" || input.type === "email") && !usr) { usr = input; } else if (input.type === "password" && !psw) { psw = input; } }); return {password: psw.value, username: usr.value, psw: psw, usr: usr}; } function findByKeyValue(array, key, value) { /* 在JSON中,以键值匹配项 */ return array.findIndex(item => item[key] === value); } function showPswMgr() { if (document.getElementById("userscript-pswmgrDlg")) { return; } let newAccountList = savedAccount.slice(); // 不直接引用 let origAccountList = savedAccount.slice(); const optDlg = document.createElement('div'); optDlg.className = 'userscript-pswmgrDlg'; optDlg.id = 'userscript-pswmgrDlg'; optDlg.style.display = 'none'; document.body.appendChild(optDlg); let listHtml = newAccountList.map(item => `

${item.username} (${item.host})

${ICONS.del}

`).join(''); optDlg.innerHTML = `

管理

已保存的账户:

${listHtml}
`; optDlg.style.display = 'block'; document.addEventListener('click', onClick); function onClick(e) { if (e.target.matches('.list-item > .item-delbtn')) { const acid = e.target.parentElement.getAttribute("acid"); const index = findByKeyValue(newAccountList, 'id', acid); if (index !== -1) { newAccountList.splice(index, 1); e.target.parentElement.remove(); } } if (e.target.id === 'userscript-cancelBtn') { newAccountList = origAccountList; // 恢复原始账户列表 closeDialog(); } if (e.target.id === 'userscript-saveBtn') { savedAccount = newAccountList; // 更新全局账户列表 GM_setValue('savedAccount', savedAccount); Toast("已保存,刷新页面以应用更改"); closeDialog(); } } function closeDialog() { const optDlg = document.getElementById("userscript-pswmgrDlg"); optDlg.style.display = 'none'; setTimeout(() => { optDlg.remove(); document.removeEventListener('click', onClick); }, 110); } } function initEle(form, cx, cy) { // 创建搜索栏元素并添加到页面 const quickFill = document.createElement('div'); quickFill.className = 'userscript-quickFill'; quickFill.id = 'userscript-quickFill'; document.body.appendChild(quickFill); let html = ''; const host = getHost(); savedAccount.forEach(item => { if (item.host === host) { html += `
${item.username}
`; } }); // 设定快速填充栏HTML内容 quickFill.innerHTML = `  保存的密码: ${html}
[隐藏]
`; // 设置快速填充栏位置 quickFill.style.left = `${cx}px`; quickFill.style.top = `${cy}px`; // 选择保存过的第一个账号,自动填充到网页 const formdata = getFormData(form); let dataindex = findByKeyValue(savedAccount, 'host', host); if (dataindex !== -1) { formdata.psw.value = savedAccount[dataindex].password; formdata.usr.value = savedAccount[dataindex].username; } // 添加点击事件监听器 quickFill.addEventListener('click', function (e) { if (e.target.matches('.item')) { const acid = e.target.getAttribute("acid"); let dataindex = findByKeyValue(savedAccount, 'id', acid); formdata.psw.value = savedAccount[dataindex].password; formdata.usr.value = savedAccount[dataindex].username; } if (e.target.matches('.hideBtn')) { quickFill.style.display = 'none'; } }); } function init() { let judgeRes = isLoginPage(); if (judgeRes.isLogin) { /* 存储初始化 */ console.log("【提示】检测到登录页面"); initEle(judgeRes.obj, judgeRes.x, judgeRes.y); judgeRes.obj.addEventListener('submit', function (e) { // 获取表单输入内容 const formdata = getFormData(judgeRes.obj); const newdata = { "id": hash(getHost() + formdata.username + formdata.password).toString(), "host": getHost(), "username": formdata.username, "password": formdata.password }; // 检查是否数据重复 const oldidx = findByKeyValue(savedAccount, "host", newdata.host); if (oldidx !== -1 && savedAccount[oldidx] && savedAccount[oldidx].id === newdata.id) { return; } // 如果不是重复账号,则询问是否保存 let res = window.confirm("【询问】是否保存账号?"); if (res) { // 保存账户 savedAccount.push(newdata); GM_setValue('savedAccount', savedAccount); Toast("账号已保存!"); } }); } } /* =====[ 菜单注册 ]===== */ var menu_mgr = GM_registerMenuCommand('⚙️ 管理密码', function () { showPswMgr(); }, 'o'); (function () { 'use strict'; if(GM_getValue('savedAccount') == null || GM_getValue('savedAccount') == "" || GM_getValue('savedAccount') == undefined){ GM_setValue('savedAccount', savedAccount); } else { savedAccount = GM_getValue('savedAccount'); } var websiteThemeColor = "#FFFFFFEE"; var websiteFontColor = "#000"; GM_addStyle(` body{ -webkit-appearance:none!important; } .userscript-quickFill{ user-select:none; background-color:` + websiteThemeColor + `; color:` + websiteFontColor + `; border:1px solid #99999999; padding:2px; font-size:12px; line-height:20px; width:180px; height:fit-content; position:absolute; display:flex; flex-direction:column; overflow:hidden auto; box-sizing:border-box; z-index:100000; font-family:"Hiragino Sans GB","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif; border-radius:5px; box-shadow:0px 0px 5px #666; } .userscript-quickFill>.item{ margin:1px 0px; border-radius:5px; padding:5px 9px; width:100%; flex-basis:fit-content; flex-shrink:0; cursor:pointer; background-color:transparent; box-sizing:border-box } .userscript-quickFill>.item:hover{ background-color:rgba(128, 128, 128, 0.2); } .userscript-quickFill>.hideBtn{ margin:1px 0px; padding:5px 9px; width:100%; flex-basis:fit-content; flex-shrink:0; color:` + websiteFontColor + `; opacity:0.6; font-size:12px; font-weight:bold; box-sizing:border-box; cursor:pointer; } .userscript-pswmgrDlg{ user-select:none; background-color:` + websiteThemeColor + `; color:` + websiteFontColor + `; border:1px solid #99999999; position:fixed; top:50%; height:fit-content; left:50%; transform:translateX(-50%) translateY(-50%); width:92vw; max-width:300px; max-height:92vh; padding:15px; border-radius:15px; box-sizing:initial; z-index:100000; box-shadow:0 1px 10px #00000088; font-family:"Hiragino Sans GB","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif; } .userscript-pswmgrDlg .ctrlbtn{ border:none; background-color:transparent; padding:8px; margin:0; color:#6d7fb4; cursor:pointer; overflow:hidden; } .userscript-pswmgrDlg h3{ margin:5px; margin-bottom:15px; font-size:24px; } .userscript-pswmgrDlg .subtitle{ margin:5px 1px; font-size:16px; font-weight:400; } .userscript-pswmgrDlg .list-item{ width:calc(100% - 10px); padding:10px 5px; margin:0; display:flex; flex-direction:row; vertical-align:middle; box-sizing:initial; } .userscript-pswmgrDlg .list-item:hover{ background-color:#55555555; } .userscript-pswmgrDlg .list-item>p{ padding:0; margin:0; font-size:16px; } .userscript-pswmgrDlg .list-item>.item-title{ flex-grow:1; margin-left:5px; } .userscript-pswmgrDlg .list-item>.item-delbtn{ cursor:pointer; width:25px; } .userscript-pswmgrDlg .list-item>.item-delbtn svg{ fill:` + websiteFontColor + `; height:100%; min-height:16px; } `); init(); setTimeout(function () { if (document.querySelectorAll(".userscript-quickFill").length === 0) { init(); } }, 1000); })();